package com.ttxs.uaa.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ttxs.common.core.constant.Common;
import com.ttxs.uaa.entity.SysMenu;
import com.ttxs.uaa.entity.SysRole;
import com.ttxs.uaa.entity.SysRoleMenu;
import com.ttxs.uaa.mapper.SysMenuMapper;
import com.ttxs.uaa.service.ISysMenuService;
import com.ttxs.uaa.service.ISysRoleMenuService;
import com.ttxs.uaa.service.ISysRoleService;
import com.ttxs.uaa.vo.TreeNode;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.stream.Collectors;

import static com.ttxs.common.core.constant.Common.Menu.ONE_LEVEL_LIMIT;

;


/**
 * <p>
 * 菜单权限表 服务实现类
 * </p>
 *
 * @author 天天向上
 * @since 2021-07-26
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {
    private final ISysRoleService sysRoleService;
    private final ISysRoleMenuService sysRoleMenuService;

    @Override
    public boolean removeByIds(Collection<?> idList) {
        // 删除角色菜单关联表数据
        sysRoleMenuService.removeByMenuIds(idList);
        // 删除菜单
        return getBaseMapper().deleteBatchIds(idList) > 0;
    }

    @Override
    public boolean save(SysMenu menu) {
        if (menu.getParentId() == null) {
            menu.setParentId(0L);
        }
        if(StringUtils.hasText(menu.getHttpMethod())) {
            menu.setHttpMethod(menu.getHttpMethod().toUpperCase());
        }
        // 设置菜单显示顺序，一级菜单数量不能超过 ONE_LEVEL_LIMIT
        Long siblingsCount = getBaseMapper().selectCount(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, menu.getParentId())) + 1;
        if(menu.getParentId() == 0 && siblingsCount > ONE_LEVEL_LIMIT) {
            throw new RuntimeException("一级菜单数量不能超过" + ONE_LEVEL_LIMIT);
        }
        menu.setOrderNum(siblingsCount.intValue());
        // 判断是否自动生成增删改查子菜单
        if (menu.getRestful()!=null && menu.getRestful()) {
            createCRUD(menu);
        } else {
            super.save(menu);
        }
        return true;
    }

    private void createCRUD(SysMenu menu) {
        String component = menu.getComponent();
        String baseApiUrl = menu.getApiUrl();
        // 保存父菜单
        super.save(menu);
        // 自动生成增删改子菜单
        List<SysMenu> menus = new ArrayList<>();
        List<RestMenuItem> restMenuItems = new ArrayList<>();
        restMenuItems.add(new RestMenuItem().setMenuName("列表").setHttpMethod("GET"));
        restMenuItems.add(new RestMenuItem().setMenuName("添加").setHttpMethod("POST"));
        restMenuItems.add(new RestMenuItem().setMenuName("删除").setHttpMethod("DELETE").setApiUrl("/{id}"));
        restMenuItems.add(new RestMenuItem().setMenuName("修改").setHttpMethod("PUT"));
        restMenuItems.add(new RestMenuItem().setMenuName("详情").setHttpMethod("GET").setApiUrl("/{id}"));
        for (int i = 0; i < restMenuItems.size(); i++) {
            SysMenu newMenu = new SysMenu();
            newMenu.setParentId(menu.getMenuId());
            newMenu.setPlatformId(menu.getPlatformId());
            newMenu.setOrderNum(menu.getOrderNum() + (i + 1));
            newMenu.setComponent(null);
            newMenu.setMenuType(Common.Menu.MENU_TYPE_FUNCTION);

            RestMenuItem restMenuItem = restMenuItems.get(i);
            newMenu.setMenuName(menu.getMenuName() + restMenuItem.getMenuName());
            newMenu.setHttpMethod(restMenuItem.getHttpMethod());
            newMenu.setApiUrl(baseApiUrl + restMenuItem.getApiUrl());
            newMenu.setVisible(restMenuItem.getVisible());
            // 列表菜单处理
            if(i == 0) {
                newMenu.setComponent(component);
            }
            menus.add(newMenu);
        }
        super.saveBatch(menus);
    }

    /**
     * 获取公用的菜单
     * @return
     */
    @Override
    public List<SysMenu> getCommonMenus() {
        return getBaseMapper().selectList(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getPlatformId, "0"));
    }

    /**
     * 获取平台角色对应的菜单列表
     * @param platformId
     * @param roleNames
     * @return
     */
    @Override
    public List<SysMenu> getMenus(String platformId, String roleNames) {
        try {
            roleNames = URLDecoder.decode(roleNames, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }
        return getBaseMapper().selectListByRoleNames(platformId, roleNames.split(","));
    }

    /**
     * 获取平台菜单和公共菜单列表
     * @param platformId
     * @return
     */
    @Override
    public List<SysMenu> getMenus(String platformId) {
        List<SysMenu> allMenus = getBaseMapper().selectList(new LambdaQueryWrapper<SysMenu>()
                .eq(SysMenu::getPlatformId, platformId));
        List<SysMenu> commonMenus = getCommonMenus();
        allMenus.addAll(commonMenus);
        return allMenus;
    }

    /**
     * 获取平台菜单树
     * @param platformId
     * @return
     */
    @Override
    public List<TreeNode> getMenuTree(String platformId, String roleNames) {
        return buildTree(getMenus(platformId, roleNames), 0L);
    }

    /**
     * 获取平台菜单和公共菜单树
     * @param platformId
     * @return
     */
    @Override
    public List<TreeNode> getMenuTree(String platformId) {
        List<SysMenu> allMenus = getMenus(platformId);
        return buildTree(allMenus, 0L);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void authorize(String platformId, String roleName, String checkedMenuIds) {
        SysRole sysRole = sysRoleService.getOne(new LambdaQueryWrapper<SysRole>()
                .eq(SysRole::getRoleName, roleName).eq(SysRole::getPlatformId, platformId));
        // 已有的权限
        Set<Long> existsMenuIds = sysRoleMenuService.list(new LambdaQueryWrapper<SysRoleMenu>()
                .eq(SysRoleMenu::getRoleId, sysRole.getRoleId()))
                .stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
        // 选中的权限
        Set<Long> shouldInsertedMenuIds = Arrays.stream(checkedMenuIds.split(",")).map(Long::new)
                                    .collect(Collectors.toSet());
        // 求差集
        diff(existsMenuIds, shouldInsertedMenuIds);
        // 删除
        if(!existsMenuIds.isEmpty()) {
            sysRoleMenuService.removeByMenuIds(existsMenuIds);
        }
        // 添加
        if(!shouldInsertedMenuIds.isEmpty()) {
            ArrayList<SysRoleMenu> sysRoleMenus = new ArrayList<>();
            shouldInsertedMenuIds.forEach(id->{
                sysRoleMenus.add(new SysRoleMenu(sysRole.getRoleId(), id, platformId));
            });
            sysRoleMenuService.saveBatch(sysRoleMenus);
        }
    }

    private static void diff(Set<Long> existsMenuIds, Set<Long> checkedMenuIds) {
        Set<Long> existsCopied = new HashSet<>(existsMenuIds);
        // 从存在的id集合中删除选中的id，得到需要删除的id
        existsMenuIds.removeAll(checkedMenuIds);
        // 从选中的id集合中删除存在的id，得到需要添加的id
        checkedMenuIds.removeAll(existsCopied);
    }

    @Override
    public long[] getCheckedKey(String platformId, String roleName) {
        return this.getMenus(platformId, roleName).stream().mapToLong(item -> item.getMenuId()).toArray();
    }

    private static List<TreeNode> buildTree(List<SysMenu> menus, Long parentId) {
        List<TreeNode> tree = new ArrayList<>();
        menus.forEach(menu -> {
            if (Objects.equals(parentId, menu.getParentId())) {
                TreeNode node = new TreeNode();
                node.setId(menu.getMenuId());
                node.setPid(menu.getParentId());
                node.setTitle(menu.getMenuName());
                node.setIcon(menu.getIcon());
                node.setPath(menu.getPath());
                node.setComponent(menu.getComponent());
                node.setApiUrl(menu.getApiUrl());
                node.setHidden(menu.getVisible().equals(Common.Menu.HIDDEN) || menu.getMenuType().equals(Common.Menu.MENU_TYPE_FUNCTION));
                node.setChecked(menu.getChecked());
                node.setChildren(buildTree(menus, menu.getMenuId()));
                tree.add(node);
            }
        });
        return tree;
    }

    @Data
    @Accessors(chain = true)
    private class RestMenuItem {
        private String menuName = "";
        private String httpMethod = "";
        private String apiUrl = "";
        private String visible = "1";
    }
}
